package com.bytz.config.mybatis;

import cn.hutool.core.util.ReflectUtil;
import com.bytz.common.constant.CommonConstant;
import com.bytz.common.function.BiConsumerE;
import com.bytz.common.system.vo.LoginUser;
import com.bytz.common.util.SecurityUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

/**
 * mybatis拦截器，自动注入创建人、创建时间、修改人、修改时间
 *
 * @Author scott
 * @Date 2019-01-19
 */
@Slf4j
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class AdminMybatisInterceptor implements Interceptor {

    /**
     * 类字段Map缓存
     */
    private static final ConcurrentHashMap<Class<?>, Map<String, Field>> FIELD_MAP = new ConcurrentHashMap<>(256);

    /**
     * 处理对象值
     */
    private void treatValue(Map<String, Field> fieldMap, String fieldName, Object obj, BiConsumerE<Field, Object> consumer) throws Exception {
        Field field = fieldMap.get(fieldName);
        if (field != null) {
            synchronized (field) {
                try {
                    field.setAccessible(true);
                    consumer.accept(field, obj);
                } finally {
                    field.setAccessible(false);
                }
            }
        }
    }

    /**
     * 获取字段Map
     * @param clazz
     * @return
     */
    private Map<String, Field> getFieldMap(Class<?> clazz) {
        Map<String, Field> result = FIELD_MAP.get(clazz);
        if (result == null) {
            result = ReflectUtil.getFieldMap(clazz);

            FIELD_MAP.putIfAbsent(clazz, result);
        }

        return result;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        String sqlId = mappedStatement.getId();
        log.debug("------sqlId------" + sqlId);
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        Object parameter = invocation.getArgs()[1];
        log.debug("------sqlCommandType------" + sqlCommandType);

        if (parameter == null) {
            return invocation.proceed();
        }

        if (SqlCommandType.INSERT == sqlCommandType) {
            LoginUser sysUser = this.getLoginUser();

            Map<String, Field> fieldMap = getFieldMap(parameter.getClass());

            // 注入创建时间
            treatValue(fieldMap, "createTime", parameter, (field, obj) -> {
                Object value = field.get(obj);
                if (value == null) {
                    field.set(obj, LocalDateTime.now());
                }
            });

            // 注入更新时间
            treatValue(fieldMap, "updateTime", parameter, (field, obj) -> {
                Object value = field.get(obj);
                if (value == null) {
                    field.set(obj, LocalDateTime.now());
                }
            });

            // 注入删除标识
            treatValue(fieldMap, "delFlag", parameter, (field, obj) -> {
                Object value = field.get(obj);
                if (value == null) {
                    field.set(obj, CommonConstant.DEL_FLAG_0);
                }
            });

            if (sysUser != null) {
                // 注入创建人Id
                treatValue(fieldMap, "createBy", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        // 登录人账号
                        field.set(obj, sysUser.getId());
                    }
                });

                // 注入创建人真实姓名
                treatValue(fieldMap, "createByName", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        // 登录人账号
                        field.set(obj, sysUser.getRealname());
                    }
                });

                // 注入更新人
                treatValue(fieldMap, "updateBy", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        // 登录人账号
                        field.set(obj, sysUser.getId());
                    }
                });

                // 注入更新人
                treatValue(fieldMap, "updateByName", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        // 登录人账号
                        field.set(obj, sysUser.getRealname());
                    }
                });

                //注入部门编码
                treatValue(fieldMap, "sysOrgCode", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        field.set(obj, sysUser.getOrgCode());
                    }
                });

                //注入部门id
                treatValue(fieldMap, "deptId", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        field.set(obj, sysUser.getDeptId());
                    }
                });

                //注入部门名称
                treatValue(fieldMap, "deptName", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        field.set(obj, sysUser.getDeptName());
                    }
                });

                //注入公司Id
                treatValue(fieldMap, "companyId", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        field.set(obj, sysUser.getCompanyId());
                    }
                });

                //注入公司名称
                treatValue(fieldMap, "companyName", parameter, (field, obj) -> {
                    Object value = field.get(obj);
                    if (value == null || "".equals(value)) {
                        field.set(obj, sysUser.getCompanyName());
                    }
                });
            }
        } else if (SqlCommandType.UPDATE == sqlCommandType) {
            LoginUser sysUser = this.getLoginUser();

            if (parameter instanceof ParamMap) {
                ParamMap<?> p = (ParamMap<?>) parameter;
                //update-begin-author:scott date:20190729 for:批量更新报错issues/IZA3Q--
                if (p.containsKey("et")) {
                    parameter = p.get("et");
                } else {
                    parameter = p.get("param1");
                }
                //update-end-author:scott date:20190729 for:批量更新报错issues/IZA3Q-

                //update-begin-author:scott date:20190729 for:更新指定字段时报错 issues/#516-
                if (parameter == null) {
                    return invocation.proceed();
                }
                //update-end-author:scott date:20190729 for:更新指定字段时报错 issues/#516-
            }

            Map<String, Field> fieldMap = getFieldMap(parameter.getClass());
            if (sysUser != null) {
                // 注入更新人
                treatValue(fieldMap, "updateBy", parameter, (field, obj) -> {
                    // 登录人账号
                    field.set(obj, sysUser.getId());
                });

                // 注入更新人
                treatValue(fieldMap, "updateByName", parameter, (field, obj) -> {
                    // 登录人账号
                    field.set(obj, sysUser.getRealname());
                });
            }

            // 不该更新创建时间、创建人，这里设置成null,因为null不会进行更新
            treatValue(fieldMap, "createTime", parameter, (field, obj) -> {
                field.set(obj, null);
            });
            treatValue(fieldMap, "createBy", parameter, (field, obj) -> {
                field.set(obj, null);
            });
            treatValue(fieldMap, "createByName", parameter, (field, obj) -> {
                field.set(obj, null);
            });

            // 注入更新时间
            treatValue(fieldMap, "updateTime", parameter, (field, obj) -> {
                field.set(obj, LocalDateTime.now());
            });
        }

        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // TODO Auto-generated method stub
    }

    private LoginUser getLoginUser() {
        return SecurityUtils.getLoginUser();
    }

}
